/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦，本文采用木兰宽松许可证第2版]
 * 
 * https://zhiqim.org/project/zhiqim_kit/zhiqim_git.htm
 *
 * ZhiqimGit is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.git;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;

import org.zhiqim.git.objer.objectid.ObjectId;

import org.zhiqim.kernel.util.Files;

/**
 * Git文件锁，用于文件锁定和替换时原子操作
 * 1、采用的方式为先创建临时文件，
 * 2、成功再重命名为正式文件
 * 3、失败删除临时文件即可，不影响原文件
 *
 * @version v1.0.0 @author zouzhigang 2017-4-18 新建与整理
 */
public class GitFileLock implements GitConstants
{
    private final File ref;
    private final File lck;
    private FileLock fileLock;
    private boolean haveLock;
    private FileOutputStream out;
    
    private boolean needStatInformation;
    private long commitLastModified;

    public GitFileLock(File file)
    {
        this.ref = file;
        this.lck = new File(ref.getParentFile(), ref.getName() + _LOCK);
    }
    
    /******************************************************************************************/
    //参数设置和操作后获取
    /******************************************************************************************/
    
    /** 设置是否需要状态信息 */
    public void setNeedStatInformation(final boolean on)
    {
        needStatInformation = on;
    }
    
    /** 保存提交修改时间 */
    private void saveStatInformation()
    {
        if (needStatInformation)
            commitLastModified = lck.lastModified();
    }
    
    /** 获取提交修改时间 */
    public long getCommitLastModified()
    {
        return commitLastModified;
    }
    
    /******************************************************************************************/
    //尝试锁定&解锁
    /******************************************************************************************/

    /** 文件尝试加锁 */
    public boolean lock() throws IOException
    {
        Files.mkDirectory(lck.getParentFile());
        
        if (!lck.createNewFile())
        {//创建锁文件失败
            return haveLock;
        }
        
        try
        {
            out = new FileOutputStream(lck);
            fileLock = out.getChannel().tryLock();
            if (fileLock == null)
                throw new OverlappingFileLockException();
            
            haveLock = true;
        }
        catch (OverlappingFileLockException e)
        {//文件加锁异常，可能是不同的进程在文件一创建即先锁定
            haveLock = false;
            try{out.close();}catch (IOException ioe){}
            out = null;
        }
        catch (IOException e)
        {
            unlock();
            throw e;
        }
        
        return haveLock;
    }
    
    /** 解锁 */
    public void unlock()
    {
        if (out != null)
        {
            if (fileLock != null)
            {
                try{fileLock.release();}catch (IOException ioe){}
                fileLock = null;
            }
            
            try{out.close();}catch (IOException ioe){}
            out = null;
        }

        if (haveLock)
        {
            haveLock = false;
            lck.delete();
        }
    }

    /** 断言锁 */
    private void requireLock()
    {
        if (out == null)
        {
            unlock();
            throw new IllegalStateException("文件[" + ref + "]的锁获取到或已释放");
        }
    }
    
    /******************************************************************************************/
    //数据操作
    /******************************************************************************************/

    /**
     * 写入对象编号和换行符到文件中
     * 
     * @param id                对象编号
     * @throws IOException      异常
     */
    public void write(ObjectId id) throws IOException
    {
        requireLock();
        
        try
        {
            id.copyTo(out);
            out.write('\n');
            out.flush();
            fileLock.release();
            out.close();
            out = null;
        }
        catch (IOException | RuntimeException | Error e)
        {
            unlock();
            throw e;
        }
    }
    
    /**
     * 写入指定的内容到文件中
     * 
     * @param id                对象编号
     * @throws IOException      异常
     */
    public void write(byte[] content) throws IOException
    {
        requireLock();
        
        try
        {
            out.write(content);
            out.flush();
            fileLock.release();
            out.close();
            out = null;
        }
        catch (IOException | RuntimeException | Error e)
        {
            unlock();
            throw e;
        }
    }

    /** 获取锁文件输出流，作一次封装，对close方法重写释放锁 */
    public OutputStream getOutputStream()
    {
        requireLock();
        
        return new OutputStream() 
        {
            public void write(byte[] b, int o, int n) throws IOException
            {
                out.write(b, o, n);
            }

            public void write(byte[] b) throws IOException
            {
                out.write(b);
            }

            public void write(int b) throws IOException
            {
                out.write(b);
            }

            public void flush() throws IOException
            {
                out.flush();
            }

            public void close() throws IOException
            {
                try
                {
                    out.flush();
                    fileLock.release();
                    out.close();
                    out = null;
                }
                catch (IOException | RuntimeException | Error ioe)
                {
                    unlock();
                    throw ioe;
                }
            }
        };
    }

    /** 提交重命名并释放锁，要求已写入关闭流 */
    public boolean commit()
    {
        if (out != null)
        {
            unlock();
            throw new IllegalStateException("文件[" + ref + "]的锁未关闭");
        }

        saveStatInformation();
        if (lck.renameTo(ref))
        {//提交重命令成功
            return true;
        }
        
        if (!ref.exists() || ref.delete())
        {//重命名失败后，删除重试一次
            if (lck.renameTo(ref))
                return true;
        }
        
        //再次失败返回false
        unlock();
        return false;
    }

    /******************************************************************************************/
    //toString
    /******************************************************************************************/

    @Override
    public String toString()
    {
        return "GitFileLock[" + lck + ", haveLock=" + haveLock + "]";
    }
}
